package com.ruoyi;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.common.constant.CacheKeyConstants;
import com.ruoyi.common.enums.TradeType;
import com.ruoyi.common.utils.BigDecimalUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.Web3Utils;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

@SpringBootApplication
@EnableSwagger2
public class WappApplication implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(WappApplication.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private IWdyTradeService wdyTradeService;

    @Autowired
    private IWdyDealService wdyDealService;

    @Autowired
    private IWdyKlineService wdyKlineService;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private IWdyProductService wdyProductService;

    @Autowired
    private IWdyBannerService wdyBannerService;

    @Value("${wallet.ethurl}")
    private String url;

    @Value("${wallet.contract.usdt}")
    private String usdtContract;


    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(WappApplication.class, args);
        System.out.println(String.format("===============当前时间：%s================", DateUtils.getNowDate()));
    }

    @PostConstruct
    void init() throws IOException {
        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        Web3Utils.setUrl(url);
        List<String> sList = new ArrayList<>();
        sList.add(usdtContract);
        Web3Utils.setContractAddrList(sList);
        Web3Utils.init();
    }

    @Override
    public void run(String... args) throws Exception {
        cacheCommonData();
        cacheTradeData();
        initTradeList();

        new Thread(() -> {
            while(true) {
                Map<String, Object> tradeMap = null;
                boolean tradeSuccess = true;
                try {
                    tradeMap = (Map<String, Object>) redisTemplate.opsForList().rightPop(CacheKeyConstants.TRADE_LIST, 10, TimeUnit.SECONDS);
                    if(tradeMap == null) {
                        continue;
                    }
                    Integer tradeType = (Integer) tradeMap.get("tradeType");
                    Long tradeId = (Long) tradeMap.get("tradeId");
                    logger.info("交易撮合：{}-{}", tradeType, tradeId);
                    StopWatch stopWatch = new StopWatch();
                    stopWatch.start();
                    if(tradeType != TradeType.CANCEL.getCode()) {
                        wdyTradeService.match(tradeId);
                    } else {
                        wdyTradeService.cancel(tradeId);
                    }
                    stopWatch.stop();
                    logger.info("撮合耗时：{}ms", stopWatch.getLastTaskTimeMillis());
                } catch (Exception e) {
                    logger.error("撮合交易异常", e);
                    if(tradeMap != null) {
                        redisTemplate.opsForList().rightPush(CacheKeyConstants.TRADE_LIST, tradeMap);
                    }
                    tradeSuccess = false;
                }
                if(tradeSuccess) {
                    redisTemplate.opsForList().leftPush(CacheKeyConstants.CACHE_TRADE_LIST, new Object());
                }
            }
        }).start();

        new Thread(() -> {
            while(true) {
                try {
                    redisTemplate.opsForList().rightPop(CacheKeyConstants.CACHE_TRADE_LIST, 3600, TimeUnit.SECONDS);
                    cacheTradeData();
                } catch (Exception e) {
                    logger.error("cacheTradeData异常", e);
                }
            }
        }).start();
    }

    private void initTradeList() {
        redisTemplate.delete(CacheKeyConstants.TRADE_LIST);

        QueryWrapper<WdyTrade> tradeListQuery = new QueryWrapper<>();
        tradeListQuery.select("id", "trade_type");
        tradeListQuery.eq("trade_state", WdyTrade.STATE_TRADE);
        tradeListQuery.eq("trade_type", TradeType.BUY.getCode());
        tradeListQuery.orderByDesc("price");
        tradeListQuery.orderByAsc("id");
        List<WdyTrade> list = wdyTradeService.list(tradeListQuery);
        logger.info("初始化交易队列：{}", list.size());
        if(!CollectionUtils.isEmpty(list)) {
            for(WdyTrade trade : list) {
                Map<String, Object> tradeMap = new HashMap<>();
                tradeMap.put("tradeType", trade.getTradeType());
                tradeMap.put("tradeId", trade.getId());
                redisTemplate.opsForList().leftPush(CacheKeyConstants.TRADE_LIST, tradeMap);
            }
        }
    }

    private void cacheCommonData() {
        cacheConfig();
        cacheBanner();
        cacheProduct();
    }

    private void cacheBanner() {
        int type = 1;
        QueryWrapper<WdyBanner> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("banner_type", type);
        List<WdyBanner> list =  wdyBannerService.list(queryWrapper);
        String cacheKey = CacheKeyConstants.getTempletString(CacheKeyConstants.BANNER, String.valueOf(type));
        redisTemplate.opsForValue().set(cacheKey, list);
    }

    private void cacheTradeData() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        cacheDealList(60);
        cacheMarketDepth("5");
        cacheMarketDepth("20");
        cacheLatestPrice();
        stopWatch.stop();
        logger.info("cacheTradeData耗时：{}ms", stopWatch.getLastTaskTimeMillis());
    }

    private void cacheProduct() {
        List<WdyProduct> list = wdyProductService.list();
        if(!CollectionUtils.isEmpty(list)) {
            for(WdyProduct product : list) {
                String cacheKey = CacheKeyConstants.getTempletString(CacheKeyConstants.PRODUCT, String.valueOf(product.getId()));
                redisTemplate.opsForValue().set(cacheKey, product);
            }
        }
    }

    private void cacheConfig() {
        sysConfigService.cacheConfig();
    }

    private void cacheDealList(int count) {
        List<WdyDeal> dealList = wdyDealService.getLastDealList(count);
        String dealListKey = CacheKeyConstants.getTempletString(CacheKeyConstants.DEALLIST, String.valueOf(count));
        redisTemplate.opsForValue().set(dealListKey, dealList);
    }

    private void cacheMarketDepth(String depth) {
        List<Map<String, Object>> buyMarketDepth = wdyTradeService.getBuyMarketDepth(depth);
        String buyMarketDepthKey = CacheKeyConstants.getTempletString(CacheKeyConstants.MARKET_DEPTH_BUY, depth);
        redisTemplate.opsForValue().set(buyMarketDepthKey, buyMarketDepth);

        List<Map<String, Object>> sellMarketDepth = wdyTradeService.getSellMarketDepth(depth);
        String sellMarketDepthKey = CacheKeyConstants.getTempletString(CacheKeyConstants.MARKET_DEPTH_SELL, depth);
        redisTemplate.opsForValue().set(sellMarketDepthKey, sellMarketDepth);
    }

    private void cacheLatestPrice() {
        BigDecimal vcRatio = new BigDecimal(sysConfigService.selectConfigByKey(CacheKeyConstants.VC_RATIO));
        redisTemplate.opsForHash().put(CacheKeyConstants.SYS_CONFIG_MAP, CacheKeyConstants.VC_RATIO, vcRatio);

        BigDecimal vcPrice = BigDecimalUtil.getP3Decimal(BigDecimalUtil.divide(1, vcRatio));

        QueryWrapper<WdyDeal> dealQuery = new QueryWrapper<>();
        dealQuery.last("limit 1");
        dealQuery.orderByDesc("create_time", "id");
        WdyDeal lastDeal = wdyDealService.getOne(dealQuery);
        if(lastDeal != null) {
            vcPrice = lastDeal.getPrice();
        }

        BigDecimal percent = BigDecimal.ZERO;
        QueryWrapper<WdyDeal> yestodayDealQuery = new QueryWrapper<>();
        yestodayDealQuery.lt("create_time", DateUtils.format(DateUtils.addDays(new Date(), -1), DateUtils.YYYY_MM_DD_HH_MM_SS));
        yestodayDealQuery.orderByDesc("create_time", "id");
        WdyDeal yestodayDeal = wdyDealService.getOne(yestodayDealQuery);
        if(yestodayDeal != null) {
            BigDecimal yestodayPrice = yestodayDeal.getPrice();
            percent = BigDecimalUtil.getP2BigDecimal(BigDecimalUtil.divide(vcPrice.subtract(yestodayPrice), yestodayPrice).multiply(BigDecimal.valueOf(100)));
        } else {
            BigDecimal initPrice = BigDecimal.valueOf(0.36);
            percent = BigDecimalUtil.getP2BigDecimal(BigDecimalUtil.divide(vcPrice.subtract(initPrice), initPrice).multiply(BigDecimal.valueOf(100)));
        }

        WdyKline kline = wdyKlineService.calKline(new Date(), WdyKline.TYPE_DAY);
        if(kline == null) {
            kline = new WdyKline();
            kline.setOpen(BigDecimal.ZERO);
            kline.setClose(BigDecimal.ZERO);
            kline.setLow(BigDecimal.ZERO);
            kline.setHigh(BigDecimal.ZERO);
            kline.setStatsTime(new Date());
        }
        Map<String, Object> map = new HashMap<>();
        map.put("vcPrice", vcPrice);
        map.put("percent", percent);
        map.put("kline", kline);
        redisTemplate.opsForHash().putAll(CacheKeyConstants.LATESTPRICE, map);
    }
}
